/*
    This file is part of Corrade.

    Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
                2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025
              Vladimír Vondruš <mosra@centrum.cz>

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.
*/

#include <cstring>

#include "Corrade/Containers/Array.h"
#include "Corrade/Containers/StaticArray.h"
#include "Corrade/Containers/String.h"
#include "Corrade/Containers/StringIterable.h"
#include "Corrade/Containers/StringView.h"
#include "Corrade/TestSuite/Tester.h"
#include "Corrade/TestSuite/Compare/Container.h"
#include "Corrade/TestSuite/Compare/Numeric.h"

namespace {

struct Str {
    Str(const char* data, std::size_t size): size{size} {
        std::memcpy(this->data, data, size);
        this->data[size] = '\0';
    }

    std::size_t size;
    char data[256];
};

}

namespace Corrade { namespace Containers {

namespace Implementation {

template<> struct StringConverter<Str> {
    static String from(const Str& other) {
        return String{other.data, other.size};
    }

    static Str to(const String& other) {
        return {other.data(), other.size()};
    }
};

}

namespace Test { namespace {

struct StringTest: TestSuite::Tester {
    explicit StringTest();

    void allocatedInitTagNoDefaultConstructor();
    void allocatedInitTagInlineDefinition();

    void constructDefault();
    void constructTakeOwnership();
    void constructTakeOwnershipConst();
    void constructTakeOwnershipImplicitSize();
    void constructTakeOwnershipNull();
    void constructTakeOwnershipNotNullTerminated();
    #ifdef CORRADE_TARGET_32BIT
    void constructTakeOwnershipTooLarge();
    #endif
    void constructPointer();
    void constructPointerSmall();
    void constructPointerSmallAllocatedInit();
    void constructPointerNull();
    void constructPointerNullAllocatedInit();
    void constructPointerSize();
    void constructPointerSizeZero();
    void constructPointerSizeSmall();
    void constructPointerSizeSmallAllocatedInit();
    void constructPointerSizeNullZero();
    void constructPointerSizeNullZeroAllocatedInit();
    void constructPointerSizeNullNonZero();
    #ifdef CORRADE_TARGET_32BIT
    void constructPointerSizeTooLarge();
    #endif

    void constructValueInit();
    void constructValueInitSmall();
    void constructValueInitZeroSize();
    #ifdef CORRADE_TARGET_32BIT
    void constructValueInitTooLarge();
    #endif
    void constructDirectInit();
    void constructDirectInitSmall();
    void constructDirectInitZeroSize();
    #ifdef CORRADE_TARGET_32BIT
    void constructDirectInitTooLarge();
    #endif
    void constructNoInit();
    void constructNoInitSmall();
    void constructNoInitZeroSize();
    #ifdef CORRADE_TARGET_32BIT
    void constructNoInitTooLarge();
    #endif

    void constructNullTerminatedGlobalView();
    void constructNullTerminatedGlobalViewAllocatedInit();

    void constructZeroNullPointerAmbiguity();

    void convertStringView();
    void convertStringViewSmall();
    void convertStringViewSmallAllocatedInit();
    void convertMutableStringView();
    void convertMutableStringViewSmall();
    void convertMutableStringViewSmallAllocatedInit();
    void convertStringViewNullTerminatedGlobalView();

    void convertArrayView();
    void convertArrayViewSmall();
    void convertArrayViewSmallAllocatedInit();
    void convertMutableArrayView();
    void convertMutableArrayViewSmall();
    void convertMutableArrayViewSmallAllocatedInit();
    void convertArray();
    void convertArraySmall();
    void convertArraySmallAllocatedInit();
    void convertArrayCustomDeleter();
    void convertArrayNullTerminatedGlobalView();
    void convertExternal();

    void compare();
    void compareLargeToLarge();
    void compareLargeToSmall();

    void copyConstructLarge();
    void copyConstructLargeToAllocatedInit();
    void copyLargeToLarge();
    void copyLargeToSmall();
    void copyConstructSmall();
    void copyConstructSmallFromAllocatedInit();
    void copyConstructSmallFromGlobal();
    void copyConstructSmallToAllocatedInit();
    void copySmallToLarge();
    void copySmallToSmall();
    void copySmallToSmallAllocatedInit();
    void copySmallToSmallGlobal();
    void copySmallAllocatedInitToLarge();
    void copySmallAllocatedInitToSmall();
    void copySmallGlobalToLarge();
    void copySmallGlobalToSmall();

    void moveConstructLarge();
    void moveConstructLargeNullTerminatedGlobalView();
    void moveConstructLargeAllocatedInit();
    void moveConstructLargeAllocatedInitNullTerminatedGlobalView();
    void moveLargeToLarge();
    void moveLargeToLargeNullTerminatedGlobalView();
    void moveLargeToSmall();
    void moveLargeToSmallNullTerminatedGlobalView();
    void moveConstructSmall();
    void moveConstructSmallAllocatedInit();
    void moveSmallToLarge();
    void moveSmallToSmall();

    void access();
    void accessNullTerminatedGlobalView();
    void accessSmall();
    void accessInvalid();

    template<class T> void slice();
    template<class T> void slicePointer();
    template<class T> void sliceZeroNullPointerAmbiguity();

    void splitCharacter();
    void splitString();
    void splitOnAny();
    void splitOnWhitespace();

    void partitionCharacter();
    void partitionString();

    void add();
    void addNullViews();
    void multiply();
    void multiplyNullView();

    void join();
    void joinNullViews();

    void hasPrefix();
    void hasSuffix();

    template<class T> void exceptPrefix();
    void exceptPrefixInvalid();
    void exceptPrefixDisabledOverloads();
    template<class T> void exceptSuffix();
    void exceptSuffixInvalid();
    void exceptSuffixDisabledOverloads();

    template<class T> void trimmed();

    /* Tests also contains() */
    template<class T> void find();
    template<class T> void findOr();
    template<class T> void findLast();
    template<class T> void findLastOr();

    /* Tests also containsAny() */
    template<class T> void findAny();
    template<class T> void findAnyOr();
    template<class T> void findLastAny();
    template<class T> void findLastAnyOr();

    void count();

    void release();
    void releaseDeleterSmall();

    void defaultDeleter();
    void customDeleter();
    /* Unlike with Array, it's not possible to create a nullptr String with a
       custom deleter in order to guarantee that it always points to a
       null-terminated string, thus there's no customDeleterNullData() */
    void customDeleterZeroSize();
    void customDeleterMovedOutInstance();

    void dataAlignment();
};

StringTest::StringTest() {
    addTests({&StringTest::allocatedInitTagNoDefaultConstructor,
              &StringTest::allocatedInitTagInlineDefinition,

              &StringTest::constructDefault,
              &StringTest::constructTakeOwnership,
              &StringTest::constructTakeOwnershipConst,
              &StringTest::constructTakeOwnershipImplicitSize,
              &StringTest::constructTakeOwnershipNull,
              &StringTest::constructTakeOwnershipNotNullTerminated,
              #ifdef CORRADE_TARGET_32BIT
              &StringTest::constructTakeOwnershipTooLarge,
              #endif
              &StringTest::constructPointer,
              &StringTest::constructPointerSmall,
              &StringTest::constructPointerSmallAllocatedInit,
              &StringTest::constructPointerNull,
              &StringTest::constructPointerNullAllocatedInit,
              &StringTest::constructPointerSize,
              &StringTest::constructPointerSizeZero,
              &StringTest::constructPointerSizeSmall,
              &StringTest::constructPointerSizeSmallAllocatedInit,
              &StringTest::constructPointerSizeNullZero,
              &StringTest::constructPointerSizeNullZeroAllocatedInit,
              &StringTest::constructPointerSizeNullNonZero,
              #ifdef CORRADE_TARGET_32BIT
              &StringTest::constructPointerSizeTooLarge,
              #endif

              &StringTest::constructValueInit,
              &StringTest::constructValueInitSmall,
              &StringTest::constructValueInitZeroSize,
              #ifdef CORRADE_TARGET_32BIT
              &StringTest::constructValueInitTooLarge,
              #endif
              &StringTest::constructDirectInit,
              &StringTest::constructDirectInitSmall,
              &StringTest::constructDirectInitZeroSize,
              #ifdef CORRADE_TARGET_32BIT
              &StringTest::constructDirectInitTooLarge,
              #endif
              &StringTest::constructNoInit,
              &StringTest::constructNoInitSmall,
              &StringTest::constructNoInitZeroSize,
              #ifdef CORRADE_TARGET_32BIT
              &StringTest::constructNoInitTooLarge,
              #endif

              &StringTest::constructNullTerminatedGlobalView,
              &StringTest::constructNullTerminatedGlobalViewAllocatedInit,

              &StringTest::constructZeroNullPointerAmbiguity,

              &StringTest::convertStringView,
              &StringTest::convertStringViewSmall,
              &StringTest::convertStringViewSmallAllocatedInit,
              &StringTest::convertMutableStringView,
              &StringTest::convertMutableStringViewSmall,
              &StringTest::convertMutableStringViewSmallAllocatedInit,
              &StringTest::convertStringViewNullTerminatedGlobalView,

              &StringTest::convertArrayView,
              &StringTest::convertArrayViewSmall,
              &StringTest::convertArrayViewSmallAllocatedInit,
              &StringTest::convertMutableArrayView,
              &StringTest::convertMutableArrayViewSmall,
              &StringTest::convertMutableArrayViewSmallAllocatedInit,
              &StringTest::convertArray,
              &StringTest::convertArraySmall,
              &StringTest::convertArraySmallAllocatedInit,
              &StringTest::convertArrayCustomDeleter,
              &StringTest::convertArrayNullTerminatedGlobalView,
              &StringTest::convertExternal,

              &StringTest::compare,
              &StringTest::compareLargeToLarge,
              &StringTest::compareLargeToSmall,

              &StringTest::copyConstructLarge,
              &StringTest::copyConstructLargeToAllocatedInit,
              &StringTest::copyLargeToLarge,
              &StringTest::copyLargeToSmall,
              &StringTest::copyConstructSmall,
              &StringTest::copyConstructSmallFromAllocatedInit,
              &StringTest::copyConstructSmallFromGlobal,
              &StringTest::copyConstructSmallToAllocatedInit,
              &StringTest::copySmallToLarge,
              &StringTest::copySmallToSmall,
              &StringTest::copySmallToSmallAllocatedInit,
              &StringTest::copySmallToSmallGlobal,
              &StringTest::copySmallAllocatedInitToLarge,
              &StringTest::copySmallAllocatedInitToSmall,
              &StringTest::copySmallGlobalToLarge,
              &StringTest::copySmallGlobalToSmall,

              &StringTest::moveConstructLarge,
              &StringTest::moveConstructLargeNullTerminatedGlobalView,
              &StringTest::moveConstructLargeAllocatedInit,
              &StringTest::moveConstructLargeAllocatedInitNullTerminatedGlobalView,
              &StringTest::moveLargeToLarge,
              &StringTest::moveLargeToLargeNullTerminatedGlobalView,
              &StringTest::moveLargeToSmall,
              &StringTest::moveLargeToSmallNullTerminatedGlobalView,
              &StringTest::moveConstructSmall,
              &StringTest::moveConstructSmallAllocatedInit,
              &StringTest::moveSmallToLarge,
              &StringTest::moveSmallToSmall,

              &StringTest::access,
              &StringTest::accessNullTerminatedGlobalView,
              &StringTest::accessSmall,
              &StringTest::accessInvalid,

              &StringTest::slice<const String>,
              &StringTest::slice<String>,
              &StringTest::slicePointer<const String>,
              &StringTest::slicePointer<String>,
              &StringTest::sliceZeroNullPointerAmbiguity<const String>,
              &StringTest::sliceZeroNullPointerAmbiguity<String>,

              &StringTest::splitCharacter,
              &StringTest::splitString,
              &StringTest::splitOnAny,
              &StringTest::splitOnWhitespace,

              &StringTest::partitionCharacter,
              &StringTest::partitionString,

              &StringTest::add,
              &StringTest::addNullViews,
              &StringTest::multiply,
              &StringTest::multiplyNullView,

              &StringTest::join,
              &StringTest::joinNullViews,

              &StringTest::hasPrefix,
              &StringTest::hasSuffix,

              &StringTest::exceptPrefix<String>,
              &StringTest::exceptPrefix<const String>,
              &StringTest::exceptPrefixInvalid,
              &StringTest::exceptPrefixDisabledOverloads,
              &StringTest::exceptSuffix<String>,
              &StringTest::exceptSuffix<const String>,
              &StringTest::exceptSuffixInvalid,
              &StringTest::exceptSuffixDisabledOverloads,

              &StringTest::trimmed<String>,
              &StringTest::trimmed<const String>,

              &StringTest::find<String>,
              &StringTest::find<const String>,
              &StringTest::findOr<String>,
              &StringTest::findOr<const String>,
              &StringTest::findLast<String>,
              &StringTest::findLast<const String>,
              &StringTest::findLastOr<String>,
              &StringTest::findLastOr<const String>,

              &StringTest::findAny<String>,
              &StringTest::findAny<const String>,
              &StringTest::findAnyOr<String>,
              &StringTest::findAnyOr<const String>,
              &StringTest::findLastAny<String>,
              &StringTest::findLastAny<const String>,
              &StringTest::findLastAnyOr<String>,
              &StringTest::findLastAnyOr<const String>,

              &StringTest::count,

              &StringTest::release,
              &StringTest::releaseDeleterSmall,

              &StringTest::defaultDeleter,
              &StringTest::customDeleter,
              &StringTest::customDeleterZeroSize,
              &StringTest::customDeleterMovedOutInstance,

              &StringTest::dataAlignment});
}

template<class T> struct ConstTraits;
template<> struct ConstTraits<String> {
    typedef MutableStringView ViewType;
    static const char* name() { return "String"; }
};
template<> struct ConstTraits<const String> {
    typedef StringView ViewType;
    static const char* name() { return "const String"; }
};

using namespace Literals;

/** @todo move these to TagsTest once the tags gets used outside of String */
void StringTest::allocatedInitTagNoDefaultConstructor() {
    /* Isn't default constructible to prevent ambiguity when calling
       foo({}) if both foo(TagT) and foo(whatever) is available */
    CORRADE_VERIFY(!std::is_default_constructible<AllocatedInitT>::value);
}

void StringTest::allocatedInitTagInlineDefinition() {
    /* Just a sanity check that the types match */
    CORRADE_VERIFY(std::is_same<decltype(AllocatedInit), const AllocatedInitT>::value);
}

void StringTest::constructDefault() {
    String a;
    CORRADE_VERIFY(!a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(a.isEmpty());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_VERIFY(a.data());
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::constructTakeOwnership() {
    /* Arguments passed to deleter and cases when deleter is called tested more
       thoroughly in customDeleter*() */

    char data[] = "hello\0world!";

    {
        String a{data, 12, [](char* data, std::size_t size) {
            ++data[0];
            data[size - 1] = '?';
        }};
        CORRADE_VERIFY(a);
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_VERIFY(!a.isEmpty());
        CORRADE_COMPARE(a.size(), sizeof(data) - 1);
        CORRADE_COMPARE(static_cast<const void*>(a.data()), data);
        CORRADE_VERIFY(a.deleter());
    }

    CORRADE_COMPARE((StringView{data, 12}), "iello\0world?"_s);
}

void StringTest::constructTakeOwnershipConst() {
    /* Taking over a const pointer should be possible, as it's a common use
       case to avoid copying a static string literal (or, further wrapped by
       String::nullTerminatedGlobalView()). Not mutating the data is then user
       responsibility. */

    const char* data = "hello\0world!";

    {
        String a{data, 12, [](char*, std::size_t) {}};
        CORRADE_VERIFY(a);
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_VERIFY(!a.isEmpty());
        CORRADE_COMPARE(a.size(), 12);
        CORRADE_COMPARE(static_cast<const void*>(a.data()), data);
        CORRADE_VERIFY(a.deleter());
    }

    CORRADE_COMPARE((StringView{data, 12}), "hello\0world!"_s);
}

void StringTest::constructTakeOwnershipImplicitSize() {
    /* Arguments passed to deleter and cases when deleter is called tested more
       thoroughly in customDeleter*() */

    /* Everything after \0 gets ignored */
    char data[] = "hello\0world!";

    {
        String a{data, [](char* data, std::size_t size) {
            ++data[0];
            data[size - 1] = '?';
        }};
        CORRADE_VERIFY(a);
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_VERIFY(!a.isEmpty());
        CORRADE_COMPARE(a.size(), 5);
        CORRADE_COMPARE(static_cast<const void*>(a.data()), data);
        CORRADE_VERIFY(a.deleter());
    }

    CORRADE_COMPARE((StringView{data, 12}), "iell?\0world!"_s);
}

void StringTest::constructTakeOwnershipNull() {
    CORRADE_SKIP_IF_NO_ASSERT();

    const char* data = nullptr;

    Containers::String out;
    Error redirectError{&out};
    String a{data, 5, [](char*, std::size_t) {}};
    String b{data, [](char*, std::size_t) {}};
    CORRADE_COMPARE(out,
        "Containers::String: can only take ownership of a non-null null-terminated array\n"
        "Containers::String: can only take ownership of a non-null null-terminated array\n");
}

void StringTest::constructTakeOwnershipNotNullTerminated() {
    CORRADE_SKIP_IF_NO_ASSERT();

    const char data[] { 'a', '3' };

    Containers::String out;
    Error redirectError{&out};
    String a{data, 1, [](char*, std::size_t) {}};
    /* The size-less constructor uses strlen(), so it will either find a \0
       somewhere random or will crash */
    CORRADE_COMPARE(out, "Containers::String: can only take ownership of a non-null null-terminated array\n");
}

#ifdef CORRADE_TARGET_32BIT
void StringTest::constructTakeOwnershipTooLarge() {
    CORRADE_SKIP_IF_NO_ASSERT();

    const char* data = "abc";

    Containers::String out;
    Error redirectError{&out};
    String a{data, std::size_t{1} << (sizeof(std::size_t)*8 - 2), [](char*, std::size_t) {}};
    /* Can't really test this with the size-less constructor */
    CORRADE_COMPARE(out,
        "Containers::String: string expected to be smaller than 2^30 bytes, got 1073741824\n");
}
#endif

void StringTest::constructPointer() {
    String a = "Allocated hello for a verbose world\0that rules";
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 35);
    CORRADE_COMPARE(a.data()[0], 'A');
    CORRADE_COMPARE(a.data()[a.size() - 1], 'd');
    CORRADE_COMPARE(a.data()[a.size()], '\0');
    CORRADE_VERIFY(!a.deleter());
}

void StringTest::constructPointerSmall() {
    String a = "hello\0world!";
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 5);
    CORRADE_COMPARE(a.data()[0], 'h');
    CORRADE_COMPARE(a.data()[a.size() - 1], 'o');
    CORRADE_COMPARE(a.data()[a.size()], '\0');

    /* Verify the data is really stored inside */
    CORRADE_VERIFY(a.data() >= reinterpret_cast<char*>(&a));
    CORRADE_VERIFY(a.data() < reinterpret_cast<char*>(&a + 1));
}

void StringTest::constructPointerSmallAllocatedInit() {
    String a{AllocatedInit, "hello\0world!"};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 5);
    CORRADE_COMPARE(a.data()[0], 'h');
    CORRADE_COMPARE(a.data()[a.size() - 1], 'o');
    CORRADE_COMPARE(a.data()[a.size()], '\0');
}

void StringTest::constructPointerNull() {
    String a = nullptr;
    CORRADE_VERIFY(!a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(a.isEmpty());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::constructPointerNullAllocatedInit() {
    String a{AllocatedInit, nullptr};
    CORRADE_VERIFY(!a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(a.isEmpty());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::constructPointerSize() {
    /* `that rules` doesn't get copied */
    String a{"Allocated hello\0for a verbose world\0that rules", 35};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 35);
    CORRADE_COMPARE(a.data()[0], 'A');
    CORRADE_COMPARE(a.data()[a.size() - 1], 'd');
    CORRADE_COMPARE(a.data()[a.size()], '\0');
}

void StringTest::constructPointerSizeZero() {
    String a{"Allocated hello for a verbose world", 0};
    CORRADE_VERIFY(!a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(a.isEmpty());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::constructPointerSizeSmall() {
    String a{"this\0world\0is hell", 10}; /* `is hell` doesn't get copied */
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a.data()[0], 't');
    CORRADE_COMPARE(a.data()[a.size() - 1], 'd');
    CORRADE_COMPARE(a.data()[a.size()], '\0');
}

void StringTest::constructPointerSizeSmallAllocatedInit() {
    String a{AllocatedInit, "this\0world\0is hell", 10};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a.data()[0], 't');
    CORRADE_COMPARE(a.data()[a.size() - 1], 'd');
    CORRADE_COMPARE(a.data()[a.size()], '\0');
}

void StringTest::constructPointerSizeNullZero() {
    String a{nullptr, 0};
    CORRADE_VERIFY(!a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(a.isEmpty());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::constructPointerSizeNullZeroAllocatedInit() {
    String a{AllocatedInit, nullptr, 0};
    CORRADE_VERIFY(!a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(a.isEmpty());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::constructPointerSizeNullNonZero() {
    CORRADE_SKIP_IF_NO_ASSERT();

    Containers::String out;
    Error redirectError{&out};
    String a{nullptr, 3};
    String aa{AllocatedInit, nullptr, 3};
    CORRADE_COMPARE(out,
        "Containers::String: received a null string of size 3\n"
        "Containers::String: received a null string of size 3\n");
}

#ifdef CORRADE_TARGET_32BIT
void StringTest::constructPointerSizeTooLarge() {
    CORRADE_SKIP_IF_NO_ASSERT();

    Containers::String out;
    Error redirectError{&out};
    String a{"abc", std::size_t{1} << (sizeof(std::size_t)*8 - 2)};
    String aa{AllocatedInit, "abc", std::size_t{1} << (sizeof(std::size_t)*8 - 2)};
    CORRADE_COMPARE(out,
        "Containers::String: string expected to be smaller than 2^30 bytes, got 1073741824\n"
        "Containers::String: string expected to be smaller than 2^30 bytes, got 1073741824\n");
}
#endif

void StringTest::constructValueInit() {
    String a{Corrade::ValueInit, 35};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 35);
    CORRADE_COMPARE(a.data()[0], '\0');
    CORRADE_COMPARE(a.data()[a.size() - 1], '\0');
    CORRADE_COMPARE(a.data()[a.size()], '\0');
}

void StringTest::constructValueInitSmall() {
    String a{Corrade::ValueInit, 10};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a.data()[0], '\0');
    CORRADE_COMPARE(a.data()[a.size() - 1], '\0');
    CORRADE_COMPARE(a.data()[a.size()], '\0');

    /* Verify the data is really stored inside */
    CORRADE_VERIFY(a.data() >= reinterpret_cast<char*>(&a));
    CORRADE_VERIFY(a.data() < reinterpret_cast<char*>(&a + 1));
}

void StringTest::constructValueInitZeroSize() {
    String a{Corrade::ValueInit, 0};
    CORRADE_VERIFY(!a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(a.isEmpty());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_COMPARE(a.data()[0], '\0');

    /* Verify the data is really stored inside */
    CORRADE_VERIFY(a.data() >= reinterpret_cast<char*>(&a));
    CORRADE_VERIFY(a.data() < reinterpret_cast<char*>(&a + 1));
}

#ifdef CORRADE_TARGET_32BIT
void StringTest::constructValueInitTooLarge() {
    CORRADE_SKIP_IF_NO_ASSERT();

    Containers::String out;
    Error redirectError{&out};
    String a{Corrade::ValueInit, std::size_t{1} << (sizeof(std::size_t)*8 - 2)};
    CORRADE_COMPARE(out,
        "Containers::String: string expected to be smaller than 2^30 bytes, got 1073741824\n");
}
#endif

void StringTest::constructDirectInit() {
    String a{Corrade::DirectInit, 35, 'X'};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 35);
    CORRADE_COMPARE(a.data()[0], 'X');
    CORRADE_COMPARE(a.data()[a.size() - 1], 'X');
    CORRADE_COMPARE(a.data()[a.size()], '\0');
}

void StringTest::constructDirectInitSmall() {
    String a{Corrade::DirectInit, 10, 'X'};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a.data()[0], 'X');
    CORRADE_COMPARE(a.data()[a.size() - 1], 'X');
    CORRADE_COMPARE(a.data()[a.size()], '\0');

    /* Verify the data is really stored inside */
    CORRADE_VERIFY(a.data() >= reinterpret_cast<char*>(&a));
    CORRADE_VERIFY(a.data() < reinterpret_cast<char*>(&a + 1));
}

void StringTest::constructDirectInitZeroSize() {
    String a{Corrade::DirectInit, 0, 'X'};
    CORRADE_VERIFY(!a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(a.isEmpty());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_COMPARE(a.data()[0], '\0');

    /* Verify the data is really stored inside */
    CORRADE_VERIFY(a.data() >= reinterpret_cast<char*>(&a));
    CORRADE_VERIFY(a.data() < reinterpret_cast<char*>(&a + 1));
}

#ifdef CORRADE_TARGET_32BIT
void StringTest::constructDirectInitTooLarge() {
    CORRADE_SKIP_IF_NO_ASSERT();

    Containers::String out;
    Error redirectError{&out};
    String a{Corrade::DirectInit, std::size_t{1} << (sizeof(std::size_t)*8 - 2), 'X'};
    CORRADE_COMPARE(out,
        "Containers::String: string expected to be smaller than 2^30 bytes, got 1073741824\n");
}
#endif

void StringTest::constructNoInit() {
    String a{Corrade::NoInit, 35};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 35);
    /* Contents can be just anything */
    CORRADE_COMPARE(a.data()[a.size()], '\0');
}

void StringTest::constructNoInitSmall() {
    String a{Corrade::NoInit, 10};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    /* Contents can be just anything */
    CORRADE_COMPARE(a.data()[a.size()], '\0');

    /* Verify the data is really stored inside */
    CORRADE_VERIFY(a.data() >= reinterpret_cast<char*>(&a));
    CORRADE_VERIFY(a.data() < reinterpret_cast<char*>(&a + 1));
}

void StringTest::constructNoInitZeroSize() {
    String a{Corrade::ValueInit, 0};
    CORRADE_VERIFY(!a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_VERIFY(a.isEmpty());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_COMPARE(a.data()[0], '\0');

    /* Verify the data is really stored inside */
    CORRADE_VERIFY(a.data() >= reinterpret_cast<char*>(&a));
    CORRADE_VERIFY(a.data() < reinterpret_cast<char*>(&a + 1));
}

#ifdef CORRADE_TARGET_32BIT
void StringTest::constructNoInitTooLarge() {
    CORRADE_SKIP_IF_NO_ASSERT();

    Containers::String out;
    Error redirectError{&out};
    String a{Corrade::NoInit, std::size_t{1} << (sizeof(std::size_t)*8 - 2)};
    CORRADE_COMPARE(out,
        "Containers::String: string expected to be smaller than 2^30 bytes, got 1073741824\n");
}
#endif

void StringTest::constructNullTerminatedGlobalView() {
    /* For a local non-null-terminated string, both convert it to an owning
       copy */
    StringView local{"Hello!", 6};
    CORRADE_COMPARE(local.flags(), StringViewFlags{});
    {
        String a = String::nullTerminatedView(local);
        String b = String::nullTerminatedGlobalView(local);
        CORRADE_COMPARE(b, local);
        CORRADE_COMPARE(a, local);
        CORRADE_VERIFY(a.isSmall());
        CORRADE_VERIFY(b.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_VERIFY(static_cast<void*>(a.data()) != local.data());
        CORRADE_VERIFY(static_cast<void*>(b.data()) != local.data());
    }

    /* For a local null-terminated only second does */
    StringView localNullTerminated = "Hello!";
    CORRADE_COMPARE(localNullTerminated.flags(), StringViewFlag::NullTerminated);
    {
        String a = String::nullTerminatedView(localNullTerminated);
        String b = String::nullTerminatedGlobalView(localNullTerminated);
        CORRADE_COMPARE(a, localNullTerminated);
        CORRADE_COMPARE(b, localNullTerminated);
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(b.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(static_cast<void*>(a.data()), localNullTerminated.data());
        CORRADE_VERIFY(static_cast<void*>(b.data()) != localNullTerminated.data());
        CORRADE_VERIFY(a.deleter());
        /* b is small, has no deleter */
    }

    /* For a global null-terminated string, both keep a view */
    StringView globalNullTerminated = "Hello!"_s;
    CORRADE_COMPARE(globalNullTerminated.flags(), StringViewFlag::Global|StringViewFlag::NullTerminated);
    {
        String a = String::nullTerminatedView(globalNullTerminated);
        String b = String::nullTerminatedGlobalView(globalNullTerminated);
        CORRADE_COMPARE(a, globalNullTerminated);
        CORRADE_COMPARE(b, globalNullTerminated);
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(!b.isSmall());
        /* The Global flag is set even in nullTerminatedView() because why
           not */
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
        CORRADE_COMPARE(static_cast<void*>(a.data()), globalNullTerminated.data());
        CORRADE_COMPARE(static_cast<void*>(b.data()), globalNullTerminated.data());
        CORRADE_VERIFY(a.deleter());
        CORRADE_VERIFY(b.deleter());
    }

    /* For a global non-null-terminated string, neither keeps a view */
    StringView global{"Hello!", 6, StringViewFlag::Global};
    CORRADE_COMPARE(global.flags(), StringViewFlag::Global);
    {
        String a = String::nullTerminatedView(global);
        String b = String::nullTerminatedGlobalView(global);
        CORRADE_COMPARE(a, global);
        CORRADE_COMPARE(b, global);
        CORRADE_VERIFY(a.isSmall());
        CORRADE_VERIFY(b.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_VERIFY(static_cast<void*>(a.data()) != global.data());
        CORRADE_VERIFY(static_cast<void*>(b.data()) != global.data());
        /* both a and b is small, has no deleter */
    }

    /* A null view is a special case. It has the flags, but a non-owning String
       can't guarantee the null-termination so an owning empty instance has to
       be made instead. */
    StringView null;
    CORRADE_VERIFY(!null.data());
    CORRADE_COMPARE(null.flags(), StringViewFlag::Global);
    {
        String a = String::nullTerminatedView(null);
        String b = String::nullTerminatedGlobalView(null);
        CORRADE_COMPARE(a, null);
        CORRADE_COMPARE(b, null);
        CORRADE_VERIFY(a.isSmall());
        CORRADE_VERIFY(b.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_VERIFY(static_cast<void*>(a.data()) != null.data());
        CORRADE_VERIFY(static_cast<void*>(b.data()) != null.data());
        /* both a and b is small, has no deleter */
    }

    /* Verify that the extra bits are cleared for all accessor APIs */
    for(StringView view: {local, localNullTerminated, global, globalNullTerminated, null}) {
        CORRADE_ITERATION(view);

        String a = String::nullTerminatedView(view);
        String b = String::nullTerminatedGlobalView(view);

        CORRADE_COMPARE(!!a, !!view);
        CORRADE_COMPARE(!!b, !!view);
        CORRADE_COMPARE(a.isEmpty(), view.isEmpty());
        CORRADE_COMPARE(b.isEmpty(), view.isEmpty());
        CORRADE_COMPARE(a.size(), view.size());
        CORRADE_COMPARE(b.size(), view.size());
    }
}

void StringTest::constructNullTerminatedGlobalViewAllocatedInit() {
    /* For a local non-null-terminated string, both convert it to an owning
       copy */
    StringView local{"Hello!", 6};
    CORRADE_COMPARE(local.flags(), StringViewFlags{});
    {
        String a = String::nullTerminatedView(AllocatedInit, local);
        String b = String::nullTerminatedGlobalView(AllocatedInit, local);
        CORRADE_COMPARE(a, local);
        CORRADE_COMPARE(b, local);
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_VERIFY(static_cast<void*>(a.data()) != local.data());
        CORRADE_VERIFY(static_cast<void*>(b.data()) != local.data());
        CORRADE_VERIFY(!a.deleter());
        CORRADE_VERIFY(!b.deleter());
    }

    /* For a local null-terminated only second does */
    StringView localNullTerminated = "Hello!";
    CORRADE_COMPARE(localNullTerminated.flags(), StringViewFlag::NullTerminated);
    {
        String a = String::nullTerminatedView(AllocatedInit, localNullTerminated);
        String b = String::nullTerminatedGlobalView(AllocatedInit, localNullTerminated);
        CORRADE_COMPARE(a, localNullTerminated);
        CORRADE_COMPARE(b, localNullTerminated);
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(static_cast<void*>(a.data()), localNullTerminated.data());
        CORRADE_VERIFY(static_cast<void*>(b.data()) != localNullTerminated.data());
        CORRADE_VERIFY(a.deleter());
        CORRADE_VERIFY(!b.deleter());
    }

    /* For a global null-terminated string, both keep a view */
    StringView globalNullTerminated = "Hello!"_s;
    CORRADE_COMPARE(globalNullTerminated.flags(), StringViewFlag::Global|StringViewFlag::NullTerminated);
    {
        String a = String::nullTerminatedView(AllocatedInit, globalNullTerminated);
        String b = String::nullTerminatedGlobalView(AllocatedInit, globalNullTerminated);
        CORRADE_COMPARE(a, globalNullTerminated);
        CORRADE_COMPARE(b, globalNullTerminated);
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(!b.isSmall());
        /* The Global flag is set even in nullTerminatedView() because why
           not */
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
        CORRADE_COMPARE(static_cast<void*>(a.data()), globalNullTerminated.data());
        CORRADE_COMPARE(static_cast<void*>(b.data()), globalNullTerminated.data());
        CORRADE_VERIFY(a.deleter());
        CORRADE_VERIFY(b.deleter());
    }

    /* For a global non-null-terminated string, neither keeps a view */
    StringView global{"Hello!", 6, StringViewFlag::Global};
    CORRADE_COMPARE(global.flags(), StringViewFlag::Global);
    {
        String a = String::nullTerminatedView(AllocatedInit, global);
        String b = String::nullTerminatedGlobalView(AllocatedInit, global);
        CORRADE_COMPARE(a, global);
        CORRADE_COMPARE(b, global);
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_VERIFY(static_cast<void*>(a.data()) != global.data());
        CORRADE_VERIFY(static_cast<void*>(b.data()) != global.data());
        CORRADE_VERIFY(!a.deleter());
        CORRADE_VERIFY(!b.deleter());
    }

    /* A null view is a special case. It has the flags, but a non-owning String
       can't guarantee the null-termination so an owning empty instance has to
       be made instead. */
    StringView null;
    CORRADE_VERIFY(!null.data());
    CORRADE_COMPARE(null.flags(), StringViewFlag::Global);
    {
        String a = String::nullTerminatedView(AllocatedInit, null);
        String b = String::nullTerminatedGlobalView(AllocatedInit, null);
        CORRADE_COMPARE(a, null);
        CORRADE_COMPARE(b, null);
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
        CORRADE_VERIFY(static_cast<void*>(a.data()) != null.data());
        CORRADE_VERIFY(static_cast<void*>(b.data()) != null.data());
        CORRADE_VERIFY(!a.deleter());
        CORRADE_VERIFY(!b.deleter());
    }

    /* Verify that the extra bits are cleared for all accessor APIs */
    for(StringView view: {local, localNullTerminated, global, globalNullTerminated, null}) {
        CORRADE_ITERATION(view);

        String a = String::nullTerminatedView(view);
        String b = String::nullTerminatedGlobalView(view);

        CORRADE_COMPARE(!!a, !!view);
        CORRADE_COMPARE(!!b, !!view);
        CORRADE_COMPARE(a.isEmpty(), view.isEmpty());
        CORRADE_COMPARE(b.isEmpty(), view.isEmpty());
        CORRADE_COMPARE(a.size(), view.size());
        CORRADE_COMPARE(b.size(), view.size());
    }
}

/* Without a corresponding SFINAE check in the std::nullptr_t constructor, this
   is ambiguous, but *only* if the size_t overload has a second 64-bit
   argument. If both would be the same, it wouldn't be ambigous, if the size_t
   overload second argument was 32-bit and the other 16-bit it wouldn't be
   either. */
int integerStringOverload(std::size_t, long long) {
    return 76;
}
int integerStringOverload(const String&, int) {
    return 39;
}

void StringTest::constructZeroNullPointerAmbiguity() {
    /* Obvious cases */
    CORRADE_COMPARE(integerStringOverload(25, 2), 76);
    CORRADE_COMPARE(integerStringOverload(nullptr, 2), 39);

    /* This should pick the integer overload, not convert 0 to nullptr */
    CORRADE_COMPARE(integerStringOverload(0, 3), 76);
}

void StringTest::convertStringView() {
    const String a = "Allocated hello\0for a verbose world"_s;
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 35);
    CORRADE_COMPARE(a[0], 'A');

    StringView aView = a;
    CORRADE_COMPARE(aView.flags(), StringViewFlag::NullTerminated);
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());

    /* Explicit conversion shouldn't be ambiguous */
    StringView aView2(a);
    CORRADE_COMPARE(aView2.flags(), StringViewFlag::NullTerminated);
    CORRADE_COMPARE(aView2.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView2.data()), a.data());

    /* It shouldn't be possible to create a mutable view from a const String.
       Not using is_convertible to catch also accidental explicit
       conversions. */
    CORRADE_VERIFY(std::is_constructible<StringView, const String>::value);
    CORRADE_VERIFY(!std::is_constructible<MutableStringView, const String>::value);
}

void StringTest::convertStringViewSmall() {
    const String a = "this\0world"_s;
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a[0], 't');

    StringView aView = a;
    CORRADE_COMPARE(aView.flags(), StringViewFlag::NullTerminated);
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());
}

void StringTest::convertStringViewSmallAllocatedInit() {
    const String a{AllocatedInit, "this\0world"_s};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a[0], 't');

    StringView aView = a;
    CORRADE_COMPARE(aView.flags(), StringViewFlag::NullTerminated);
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());
}

void StringTest::convertMutableStringView() {
    char aData[] = "Allocated hello\0for a verbose world";
    String a = MutableStringView{aData, 35};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 35);
    CORRADE_COMPARE(a[0], 'A');

    MutableStringView aView = a;
    CORRADE_COMPARE(aView.flags(), StringViewFlag::NullTerminated);
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());

    /* Explicit conversion shouldn't be ambiguous */
    MutableStringView aView2(a);
    CORRADE_COMPARE(aView2.flags(), StringViewFlag::NullTerminated);
    CORRADE_COMPARE(aView2.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView2.data()), a.data());
}

void StringTest::convertMutableStringViewSmall() {
    char aData[] = "this\0world";
    String a = MutableStringView{aData, 10};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a[0], 't');

    MutableStringView aView = a;
    CORRADE_COMPARE(aView.flags(), StringViewFlag::NullTerminated);
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());
}

void StringTest::convertMutableStringViewSmallAllocatedInit() {
    char aData[] = "this\0world";
    String a{AllocatedInit, MutableStringView{aData, 10}};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a[0], 't');

    MutableStringView aView = a;
    CORRADE_COMPARE(aView.flags(), StringViewFlag::NullTerminated);
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());
}

void StringTest::convertStringViewNullTerminatedGlobalView() {
    String a = String::nullTerminatedView("null terminated"_s);
    String aAllocated = String::nullTerminatedView(AllocatedInit, "null terminated, allocated"_s);
    String aGlobal = String::nullTerminatedGlobalView("null terminated, global"_s);
    String aGlobalAllocated = String::nullTerminatedGlobalView(AllocatedInit, "null terminated, global, allocated"_s);

    /* Using pointers to avoid extra complexity from copy/move constructors
       potentially discarding the flags */
    for(String* s: {&a, &aAllocated, &aGlobal, &aGlobalAllocated}) {
        CORRADE_COMPARE(s->viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

        StringView aView = *s;
        MutableStringView aMutableView = *s;
        CORRADE_COMPARE(aView, *s);
        CORRADE_COMPARE(aMutableView, *s);
        CORRADE_COMPARE(aView.flags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
        CORRADE_COMPARE(aMutableView.flags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
    }
}

void StringTest::convertArrayView() {
    const String a = arrayView("Allocated hello\0for a verbose world").exceptSuffix(1);
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 35);
    CORRADE_COMPARE(a[0], 'A');

    ArrayView<const char> aView = a;
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());

    ArrayView<const void> aVoidView = a;
    CORRADE_COMPARE(aVoidView.size(), a.size());
    CORRADE_COMPARE(aVoidView.data(), a.data());

    /* It shouldn't be possible to create a mutable view from a const String.
       Not using is_convertible to catch also accidental explicit
       conversions. */
    CORRADE_VERIFY(std::is_constructible<ArrayView<const char>, const String>::value);
    CORRADE_VERIFY(std::is_constructible<ArrayView<const void>, const String>::value);
    CORRADE_VERIFY(!std::is_constructible<ArrayView<char>, const String>::value);
    CORRADE_VERIFY(!std::is_constructible<ArrayView<void>, const String>::value);
}

void StringTest::convertArrayViewSmall() {
    const String a = arrayView("this\0world").exceptSuffix(1);
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a[0], 't');

    ArrayView<const char> aView = a;
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());

    ArrayView<const void> aVoidView = a;
    CORRADE_COMPARE(aVoidView.size(), a.size());
    CORRADE_COMPARE(aVoidView.data(), a.data());
}

void StringTest::convertArrayViewSmallAllocatedInit() {
    const String a{AllocatedInit, arrayView("this\0world").exceptSuffix(1)};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a[0], 't');

    ArrayView<const char> aView = a;
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());

    ArrayView<const void> aVoidView = a;
    CORRADE_COMPARE(aVoidView.size(), a.size());
    CORRADE_COMPARE(aVoidView.data(), a.data());
}

void StringTest::convertMutableArrayView() {
    char aData[] = "Allocated hello\0for a verbose world";
    String a = ArrayView<char>{aData}.exceptSuffix(1);
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 35);
    CORRADE_COMPARE(a[0], 'A');

    ArrayView<char> aView = a;
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());

    ArrayView<void> aVoidView = a;
    CORRADE_COMPARE(aVoidView.size(), a.size());
    CORRADE_COMPARE(aVoidView.data(), a.data());
}

void StringTest::convertMutableArrayViewSmall() {
    char aData[] = "this\0world";
    String a = ArrayView<char>{aData}.exceptSuffix(1);
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a[0], 't');

    ArrayView<char> aView = a;
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());

    ArrayView<void> aVoidView = a;
    CORRADE_COMPARE(aVoidView.size(), a.size());
    CORRADE_COMPARE(aVoidView.data(), a.data());
}

void StringTest::convertMutableArrayViewSmallAllocatedInit() {
    char aData[] = "this\0world";
    String a{AllocatedInit, ArrayView<char>{aData}.exceptSuffix(1)};
    CORRADE_VERIFY(a);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_VERIFY(!a.isEmpty());
    CORRADE_COMPARE(a.size(), 10);
    CORRADE_COMPARE(a[0], 't');

    ArrayView<char> aView = a;
    CORRADE_COMPARE(aView.size(), a.size());
    CORRADE_COMPARE(static_cast<const void*>(aView.data()), a.data());

    ArrayView<void> aVoidView = a;
    CORRADE_COMPARE(aVoidView.size(), a.size());
    CORRADE_COMPARE(aVoidView.data(), a.data());
}

void StringTest::convertArray() {
    String a = "Allocated hello\0for a verbose world"_s;
    CORRADE_VERIFY(!a.isSmall());

    Array<char> array = Utility::move(a);
    CORRADE_COMPARE(StringView{array}, "Allocated hello\0for a verbose world"_s);
    CORRADE_COMPARE(array.deleter(), nullptr);
    /* The original allocation includes a null terminator, it should be here as
       well. Accessing directly the raw data because operator[] would trigger
       an OOB assert. */
    CORRADE_COMPARE(array.data()[array.size()], '\0');

    /* State should be the same as with release(), so of a default-constructed
       instance -- with zero size, but a non-null null-terminated data */
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_VERIFY(a.data());
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::convertArraySmall() {
    String a = "this\0world"_s;
    CORRADE_VERIFY(a.isSmall());

    Array<char> array = Utility::move(a);
    CORRADE_COMPARE(StringView{array}, "this\0world"_s);
    CORRADE_COMPARE(array.deleter(), nullptr);
    /* The original allocation includes a null terminator, it should be here as
       well. Accessing directly the raw data because operator[] would trigger
       an OOB assert. */
    CORRADE_COMPARE(array.data()[array.size()], '\0');

    /* State should be the same as with release(), so of a default-constructed
       instance -- with zero size, but a non-null null-terminated data */
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_VERIFY(a.data());
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::convertArraySmallAllocatedInit() {
    String a{AllocatedInit, "this\0world"_s};
    Array<char> array = Utility::move(a);
    CORRADE_COMPARE(StringView{array}, "this\0world"_s);
    CORRADE_COMPARE(array.deleter(), nullptr);

    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_VERIFY(a.data());
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::convertArrayCustomDeleter() {
    const char data[] = "Statically allocated hello\0for a verbose world";
    auto deleter = [](char*, std::size_t){};

    String a{data, Containers::arraySize(data) - 1, deleter};
    Array<char> array = Utility::move(a);
    CORRADE_COMPARE(StringView{array}, "Statically allocated hello\0for a verbose world"_s);
    CORRADE_COMPARE(array.deleter(), deleter);

    /* State should be the same as with release(), so of a default-constructed
       instance -- with zero size, but a non-null null-terminated data */
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_VERIFY(a.data());
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::convertArrayNullTerminatedGlobalView() {
    StringView view = "Allocated hello\0for a verbose world"_s;
    String a = String::nullTerminatedGlobalView(view);
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

    /* The size should be returned with the Global bit cleared */
    Array<char> array = Utility::move(a);
    CORRADE_COMPARE(array.size(), view.size());
    CORRADE_COMPARE(StringView{array}, view);
}

void StringTest::convertExternal() {
    Str a{"hello", 5};

    String b = a;
    CORRADE_COMPARE(b.data(), "hello"_s);
    CORRADE_COMPARE(b.size(), 5);

    Str c = b;
    CORRADE_COMPARE(c.data, "hello"_s);
    CORRADE_COMPARE(c.size, 5);
}

void StringTest::compare() {
    /* Trivial case */
    String a = "hello";
    CORRADE_VERIFY(a == a);

    String b{"hello3", 5};
    CORRADE_VERIFY(b == b);
    CORRADE_VERIFY(a == b);
    CORRADE_VERIFY(b == a);

    /* Verify we don't just compare a common prefix */
    String c = "hello!";
    CORRADE_VERIFY(a != c);
    CORRADE_VERIFY(c != a);

    /* Comparison with an empty string */
    String empty;
    CORRADE_VERIFY(empty == empty);
    CORRADE_VERIFY(a != empty);
    CORRADE_VERIFY(empty != a);

    /* Null terminator in the middle -- it should not stop at it */
    CORRADE_VERIFY(String{"hello\0world"_s} == (String{"hello\0world!", 11}));
    CORRADE_VERIFY(String{"hello\0wOrld"_s} != (String{"hello\0world!", 11}));

    /* C strings on either side */
    CORRADE_VERIFY(a == "hello");
    CORRADE_VERIFY("hello" == a);
    CORRADE_VERIFY(c != "hello");
    CORRADE_VERIFY("hello" != c);

    /* Views on either side */
    CORRADE_VERIFY(a == "hello"_s);
    CORRADE_VERIFY("hello"_s == a);
    CORRADE_VERIFY(c != "hello"_s);
    CORRADE_VERIFY("hello"_s != c);

    /* Mutable views on either side */
    char dData[] = "hello";
    MutableStringView d = dData;
    CORRADE_VERIFY(a == d);
    CORRADE_VERIFY(d == a);
    CORRADE_VERIFY(c != d);
    CORRADE_VERIFY(d != c);
}

void StringTest::compareLargeToLarge() {
    String a = "Allocated hello for a verbose world";
    CORRADE_VERIFY(!a.isSmall());

    String b = "Allocated hello for a verbose world";
    CORRADE_VERIFY(!b.isSmall());

    String c = "Allocated hello for a verbose world!";
    CORRADE_VERIFY(!c.isSmall());

    CORRADE_VERIFY(a == a);
    CORRADE_VERIFY(b == b);
    CORRADE_VERIFY(c == c);
    CORRADE_VERIFY(a == b);
    CORRADE_VERIFY(b == a);
    CORRADE_VERIFY(a != c);
    CORRADE_VERIFY(c != a);
}

void StringTest::compareLargeToSmall() {
    String a = "hello";
    CORRADE_VERIFY(a.isSmall());

    /* Create explicitly from heap-allocated data to avoid it being stored as
       SSO */
    char bData[] = "hello";
    String b{bData, 5, [](char*, std::size_t){}};
    CORRADE_VERIFY(!b.isSmall());

    char cData[] = "hello!";
    String c{cData, 6, [](char*, std::size_t){}};
    CORRADE_VERIFY(!c.isSmall());

    CORRADE_VERIFY(a == a);
    CORRADE_VERIFY(b == b);
    CORRADE_VERIFY(c == c);
    CORRADE_VERIFY(a == b);
    CORRADE_VERIFY(b == a);
    CORRADE_VERIFY(a != c);
    CORRADE_VERIFY(c != a);
}

void StringTest::copyConstructLarge() {
    char aData[] = "Allocated hello for a verbose world";

    {
        String a{aData, sizeof(aData) - 1, [](char* data, std::size_t){
            ++data[0];
        }};
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(a.deleter());

        /* A copy is made using a default deleter */
        String b = a;
        CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
        CORRADE_VERIFY(b.data() != a.data());
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_VERIFY(!b.deleter());
    }

    /* a is deallocated as usual */
    CORRADE_COMPARE(aData[0], 'B');
}

void StringTest::copyConstructLargeToAllocatedInit() {
    /* Same as above, for already-large strings it should have no difference */

    char aData[] = "Allocated hello for a verbose world";

    {
        String a{aData, sizeof(aData) - 1, [](char* data, std::size_t){
            ++data[0];
        }};
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(a.deleter());

        /* A copy is made using a default deleter */
        String b{AllocatedInit, a};
        CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
        CORRADE_VERIFY(b.data() != a.data());
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_VERIFY(!b.deleter());
    }

    /* a is deallocated as usual */
    CORRADE_COMPARE(aData[0], 'B');
}

void StringTest::copyLargeToLarge() {
    char aData[] = "Allocated hello for a verbose world";
    char bData[] = "ALLOCATED HELLO FOR A VERBOSE WORLD!!!";

    {
        String a{aData, sizeof(aData) - 1, [](char* data, std::size_t){
            ++data[0];
        }};
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(a.deleter());

        String b{bData, sizeof(bData) - 1, [](char* data, std::size_t){
            ++data[1];
        }};
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_VERIFY(b.deleter());

        /* A copy is made using a default deleter, b is deallocated */
        b = a;
        CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
        CORRADE_VERIFY(b.data() != a.data());
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_VERIFY(!b.deleter());
        CORRADE_COMPARE(bData[1], 'M');
    }

    /* a is deallocated as usual */
    CORRADE_COMPARE(aData[0], 'B');
}

void StringTest::copyLargeToSmall() {
    char aData[] = "Allocated hello for a verbose world";

    {
        String a{aData, sizeof(aData) - 1, [](char* data, std::size_t){
            ++data[0];
        }};
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(a.deleter());

        String b = "hello";
        CORRADE_VERIFY(b.isSmall());

        /* A copy is made using a default deleter, b is overwritten */
        b = a;
        CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
        CORRADE_VERIFY(b.data() != a.data());
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_VERIFY(!b.deleter());
    }

    /* a is deallocated as usual */
    CORRADE_COMPARE(aData[0], 'B');
}

void StringTest::copyConstructSmall() {
    String a = "hello";
    CORRADE_VERIFY(a.isSmall());

    /* A copy is made using a SSO */
    String b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(b.isSmall());
}

void StringTest::copyConstructSmallFromAllocatedInit() {
    String a{AllocatedInit, "hello"};
    CORRADE_VERIFY(!a.isSmall());

    /* The original wasn't a SSO so the new one isn't either */
    String b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(!b.isSmall());
}

void StringTest::copyConstructSmallFromGlobal() {
    String a = String::nullTerminatedGlobalView("hello"_s);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

    /* The original wasn't a SSO so the new one isn't either. But it also isn't
       global, it's a local copy. */
    String b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(!b.isSmall());
    CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
}

void StringTest::copyConstructSmallToAllocatedInit() {
    String a = "hello";
    CORRADE_VERIFY(a.isSmall());

    /* A copy is made using a default deleter */
    String b{AllocatedInit, a};
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(!b.isSmall());
    CORRADE_VERIFY(!b.deleter());
}

void StringTest::copySmallToLarge() {
    String a = "hello";
    CORRADE_VERIFY(a.isSmall());

    char bData[] = "ALLOCATED HELLO FOR A VERBOSE WORLD!!!";
    String b{bData, sizeof(bData) - 1, [](char* data, std::size_t){
        ++data[1];
    }};
    CORRADE_VERIFY(!b.isSmall());
    CORRADE_VERIFY(b.deleter());

    /* A copy is made using a SSO, b is deallocated */
    b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(b.isSmall());
    CORRADE_COMPARE(bData[1], 'M');
}

void StringTest::copySmallToSmall() {
    String a = "hello";
    CORRADE_VERIFY(a.isSmall());

    String b = "HELLO!!!";
    CORRADE_VERIFY(b.isSmall());

    /* A copy is made using a SSO, original data overwritten */
    b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(b.isSmall());
}

void StringTest::copySmallToSmallAllocatedInit() {
    String a = "hello";
    CORRADE_VERIFY(a.isSmall());

    String b{AllocatedInit, "HELLO!!!"};
    CORRADE_VERIFY(!b.isSmall());

    /* A copy is made using a SSO, b is deallocated, i.e. basically the same as
       copySmallToLarge() */
    b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(b.isSmall());
}

void StringTest::copySmallToSmallGlobal() {
    String a = "hello";
    CORRADE_VERIFY(a.isSmall());

    String b = String::nullTerminatedGlobalView("HELLO!!!"_s);
    CORRADE_VERIFY(!b.isSmall());
    CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

    /* A copy is made using a SSO, b is deallocated, i.e. basically the same as
       copySmallToLarge(). Since it's a copy, the global flag is lost as
       well. */
    b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(b.isSmall());
    CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
}

void StringTest::copySmallAllocatedInitToLarge() {
    String a{AllocatedInit, "hello"};
    CORRADE_VERIFY(!a.isSmall());

    char bData[] = "ALLOCATED HELLO FOR A VERBOSE WORLD!!!";
    String b{bData, sizeof(bData) - 1, [](char* data, std::size_t){
        ++data[1];
    }};
    CORRADE_VERIFY(!b.isSmall());
    CORRADE_VERIFY(b.deleter());

    /* The original wasn't a SSO so the new one isn't either, b is
       deallocated */
    b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(!b.isSmall());
    CORRADE_COMPARE(bData[1], 'M');
}

void StringTest::copySmallAllocatedInitToSmall() {
    String a{AllocatedInit, "hello"};
    CORRADE_VERIFY(!a.isSmall());

    String b = "HELLO!!!";
    CORRADE_VERIFY(b.isSmall());

    /* The original wasn't a SSO so the new one isn't either, original
       data overwritten */
    b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(!b.isSmall());
}

void StringTest::copySmallGlobalToLarge() {
    String a = String::nullTerminatedGlobalView("hello"_s);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

    char bData[] = "ALLOCATED HELLO FOR A VERBOSE WORLD!!!";
    String b{bData, sizeof(bData) - 1, [](char* data, std::size_t){
        ++data[1];
    }};
    CORRADE_VERIFY(!b.isSmall());
    CORRADE_VERIFY(b.deleter());

    /* The original wasn't a SSO so the new one isn't either. But it also isn't
       global, it's a local copy. The b is deallocated. */
    b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(!b.isSmall());
    CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
    CORRADE_COMPARE(bData[1], 'M');
}

void StringTest::copySmallGlobalToSmall() {
    String a = String::nullTerminatedGlobalView("hello"_s);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

    String b = "HELLO!!!";
    CORRADE_VERIFY(b.isSmall());

    /* The original wasn't a SSO so the new one isn't either. But it also isn't
       global, it's a local copy. The original data are overwritten. */
    b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(!b.isSmall());
    CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated);
}

void StringTest::moveConstructLarge() {
    char aData[] = "Allocated hello for a verbose world";

    {
        auto deleter = [](char* data, std::size_t){ ++data[0]; };
        String a{aData, sizeof(aData) - 1, deleter};
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(a.deleter());

        /* Everything including the deleter is moved */
        String b = Utility::move(a);
        CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
        CORRADE_VERIFY(b.data() == aData);
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_COMPARE(b.deleter(), deleter);
    }

    /* b is deallocated just once */
    CORRADE_COMPARE(aData[0], 'B');

    CORRADE_VERIFY(std::is_nothrow_move_constructible<String>::value);
}

void StringTest::moveConstructLargeNullTerminatedGlobalView() {
    StringView view = "Allocated hello for a verbose world"_s;

    String a = String::nullTerminatedGlobalView(view);
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

    /* The Global flag should be preserved */
    String b = Utility::move(a);
    CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
    CORRADE_VERIFY(b.data() == view.data());
    CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
}

void StringTest::moveConstructLargeAllocatedInit() {
    /* Same as above, for already-large strings it should have no difference */

    char aData[] = "Allocated hello for a verbose world";

    {
        auto deleter = [](char* data, std::size_t){ ++data[0]; };
        String a{aData, sizeof(aData) - 1, deleter};
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(a.deleter());

        /* Everything including the deleter is moved */
        String b{AllocatedInit, Utility::move(a)};
        CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
        CORRADE_VERIFY(b.data() == aData);
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_COMPARE(b.deleter(), deleter);
    }

    /* b is deallocated just once */
    CORRADE_COMPARE(aData[0], 'B');

    CORRADE_VERIFY(std::is_nothrow_move_constructible<String>::value);
}

void StringTest::moveConstructLargeAllocatedInitNullTerminatedGlobalView() {
    StringView view = "Allocated hello for a verbose world"_s;

    String a = String::nullTerminatedGlobalView(view);
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

    /* The Global flag should be preserved */
    String b{AllocatedInit, Utility::move(a)};
    CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
    CORRADE_VERIFY(b.data() == view.data());
    CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
}

void StringTest::moveLargeToLarge() {
    char aData[] = "Allocated hello for a verbose world";
    char bData[] = "ALLOCATED HELLO FOR A VERBOSE WORLD!!!";

    {
        String a{aData, sizeof(aData) - 1, [](char* data, std::size_t){
            ++data[0];
        }};
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(a.deleter());

        String b{bData, sizeof(bData) - 1, [](char* data, std::size_t){
            ++data[1];
        }};
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_VERIFY(b.deleter());

        /* The two are simply swapped */
        b = Utility::move(a);
        CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
        CORRADE_VERIFY(b.data() == aData);
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_VERIFY(b.deleter());

        /* No deleters fired yet */
        CORRADE_COMPARE(aData[0], 'A');
        CORRADE_COMPARE(bData[1], 'L');
    }

    /* Both deallocated as usual */
    CORRADE_COMPARE(aData[0], 'B');
    CORRADE_COMPARE(bData[1], 'M');

    CORRADE_VERIFY(std::is_nothrow_move_assignable<String>::value);
}

void StringTest::moveLargeToLargeNullTerminatedGlobalView() {
    StringView view = "Allocated hello for a verbose world"_s;
    char bData[] = "ALLOCATED HELLO FOR A VERBOSE WORLD!!!";

    {
        String a = String::nullTerminatedGlobalView(view);
        CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

        String b{bData, sizeof(bData) - 1, [](char* data, std::size_t){
            ++data[1];
        }};
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_VERIFY(b.deleter());

        /* The two are simply swapped; the Global flag should be preserved in
           B */
        b = Utility::move(a);
        CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
        CORRADE_VERIFY(b.data() == view.data());
        CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

        /* No deleter fired yet */
        CORRADE_COMPARE(bData[1], 'L');
    }

    /* a is deallocated as usual */
    CORRADE_COMPARE(bData[1], 'M');

    CORRADE_VERIFY(std::is_nothrow_move_assignable<String>::value);
}

void StringTest::moveLargeToSmall() {
    char aData[] = "Allocated hello for a verbose world";

    {
        String a{aData, sizeof(aData) - 1, [](char* data, std::size_t){
            ++data[0];
        }};
        CORRADE_VERIFY(!a.isSmall());
        CORRADE_VERIFY(a.deleter());

        String b = "hello";
        CORRADE_VERIFY(b.isSmall());

        /* The two are simply swapped */
        b = Utility::move(a);
        CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
        CORRADE_VERIFY(b.data() == aData);
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_VERIFY(b.deleter());

        /* No deleter fired yet */
        CORRADE_COMPARE(aData[0], 'A');
    }

    /* a is deallocated as usual */
    CORRADE_COMPARE(aData[0], 'B');
}

void StringTest::moveLargeToSmallNullTerminatedGlobalView() {
    StringView view = "Allocated hello for a verbose world"_s;

    String a = String::nullTerminatedGlobalView(view);
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);

    String b = "hello";
    CORRADE_VERIFY(b.isSmall());

    /* The two are simply swapped; the Global flag should be preserved in B */
    b = Utility::move(a);
    CORRADE_COMPARE(b, "Allocated hello for a verbose world"_s);
    CORRADE_VERIFY(b.data() == view.data());
    CORRADE_COMPARE(b.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
}

void StringTest::moveConstructSmall() {
    String a = "hello";
    CORRADE_VERIFY(a.isSmall());

    /* The two are simply swapped */
    String b = Utility::move(a);
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(b.isSmall());
}

void StringTest::moveConstructSmallAllocatedInit() {
    String a = "hello";
    CORRADE_VERIFY(a.isSmall());

    /* A copy is made using a default deleter */
    String b{AllocatedInit, Utility::move(a)};
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(!b.isSmall());
    CORRADE_VERIFY(!b.deleter());
}

void StringTest::moveSmallToLarge() {
    char bData[] = "ALLOCATED HELLO FOR A VERBOSE WORLD!!!";

    {
        String a = "hello";
        CORRADE_VERIFY(a.isSmall());

        String b{bData, sizeof(bData) - 1, [](char* data, std::size_t){
            ++data[1];
        }};
        CORRADE_VERIFY(!b.isSmall());
        CORRADE_VERIFY(b.deleter());

        /* The two are simply swapped */
        b = Utility::move(a);
        CORRADE_COMPARE(b, "hello"_s);
        CORRADE_VERIFY(b.data() != a.data());
        CORRADE_VERIFY(b.isSmall());

        /* No deleters fired yet */
        CORRADE_COMPARE(bData[1], 'L');
    }

    /* b deallocated as usual */
    CORRADE_COMPARE(bData[1], 'M');
}

void StringTest::moveSmallToSmall() {
    String a = "hello";
    CORRADE_VERIFY(a.isSmall());

    String b = "HELLO!!!";
    CORRADE_VERIFY(b.isSmall());

    /* The two are simply swapped */
    b = a;
    CORRADE_COMPARE(b, "hello"_s);
    CORRADE_VERIFY(b.data() != a.data());
    CORRADE_VERIFY(b.isSmall());
}

void StringTest::access() {
    String a = "Allocated hello for a verbose world";
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(*a.begin(), 'A');
    CORRADE_COMPARE(*a.cbegin(), 'A');
    CORRADE_COMPARE(a.front(), 'A');
    CORRADE_COMPARE(*(a.end() - 1), 'd');
    CORRADE_COMPARE(*(a.cend() - 1), 'd');
    CORRADE_COMPARE(a.back(), 'd');

    a[14] = '!';
    *a.begin() = 'N';
    ++a.front();
    *(a.end() - 1) = 's';
    ++a.back();
    CORRADE_COMPARE(a, "Ollocated hell! for a verbose worlt");

    const String ca = "Allocated hello for a verbose world";
    CORRADE_VERIFY(!ca.isSmall());
    CORRADE_COMPARE(*ca.begin(), 'A');
    CORRADE_COMPARE(*ca.cbegin(), 'A');
    CORRADE_COMPARE(ca.front(), 'A');
    CORRADE_COMPARE(*(ca.end() - 1), 'd');
    CORRADE_COMPARE(*(ca.cend() - 1), 'd');
    CORRADE_COMPARE(ca.back(), 'd');
    CORRADE_COMPARE(ca[14], 'o');
}

void StringTest::accessNullTerminatedGlobalView() {
    /* Like access(), but wrapping a view and not modifying any data.
       Everything should behave the same. */

    String a = String::nullTerminatedGlobalView("Allocated hello for a verbose world"_s);
    CORRADE_COMPARE(a.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE(*a.begin(), 'A');
    CORRADE_COMPARE(*a.cbegin(), 'A');
    CORRADE_COMPARE(a.front(), 'A');
    CORRADE_COMPARE(*(a.end() - 1), 'd');
    CORRADE_COMPARE(*(a.cend() - 1), 'd');
    CORRADE_COMPARE(a.back(), 'd');

    const String ca = String::nullTerminatedGlobalView("Allocated hello for a verbose world"_s);
    CORRADE_COMPARE(ca.viewFlags(), StringViewFlag::NullTerminated|StringViewFlag::Global);
    CORRADE_VERIFY(!ca.isSmall());
    CORRADE_COMPARE(*ca.begin(), 'A');
    CORRADE_COMPARE(*ca.cbegin(), 'A');
    CORRADE_COMPARE(ca.front(), 'A');
    CORRADE_COMPARE(*(ca.end() - 1), 'd');
    CORRADE_COMPARE(*(ca.cend() - 1), 'd');
    CORRADE_COMPARE(ca.back(), 'd');
    CORRADE_COMPARE(ca[14], 'o');
}

void StringTest::accessSmall() {
    String a = "hello!";
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(*a.begin(), 'h');
    CORRADE_COMPARE(*a.cbegin(), 'h');
    CORRADE_COMPARE(a.front(), 'h');
    CORRADE_COMPARE(*(a.end() - 1), '!');
    CORRADE_COMPARE(*(a.cend() - 1), '!');
    CORRADE_COMPARE(a.back(), '!');

    a[4] = '!';
    *(a.end() - 1) = '?';
    *a.begin() = 'J';
    ++a.front();
    ++a.back();
    CORRADE_COMPARE(a, "Kell!@");

    const String ca = "hello!";
    CORRADE_VERIFY(ca.isSmall());
    CORRADE_COMPARE(*ca.begin(), 'h');
    CORRADE_COMPARE(*ca.cbegin(), 'h');
    CORRADE_COMPARE(ca.front(), 'h');
    CORRADE_COMPARE(*(ca.end() - 1), '!');
    CORRADE_COMPARE(*(ca.cend() - 1), '!');
    CORRADE_COMPARE(ca.back(), '!');
}

void StringTest::accessInvalid() {
    CORRADE_SKIP_IF_NO_DEBUG_ASSERT();

    /* Use both an allocated and a SSO instance to test we're not checking the
       members directly */
    String a;
    String b = "hello";
    String bAllocated{AllocatedInit, "hello"};
    CORRADE_VERIFY(a.isSmall());
    CORRADE_VERIFY(b.isSmall());
    CORRADE_VERIFY(!bAllocated.isSmall());

    /* Accessing the null terminator is fine */
    a[0];
    b[5];
    bAllocated[5];

    Containers::String out;
    Error redirectError{&out};
    /* front() / back() should return the first ever byte before the null
       terminator and the last ever byte before the null terminator. There
       isn't any, so it asserts. */
    a.front();
    a.back();
    a[1];
    b[6];
    bAllocated[6];
    CORRADE_COMPARE(out,
        "Containers::String::front(): string is empty\n"
        "Containers::String::back(): string is empty\n"
        "Containers::String::operator[](): index 1 out of range for 0 null-terminated bytes\n"
        "Containers::String::operator[](): index 6 out of range for 5 null-terminated bytes\n"
        "Containers::String::operator[](): index 6 out of range for 5 null-terminated bytes\n");
}

template<class T> void StringTest::slice() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    T a = "hello";
    CORRADE_COMPARE(a.slice(1, 4), "ell"_s);
    CORRADE_COMPARE(a.sliceSize(1, 3), "ell"_s);
    CORRADE_COMPARE(a.prefix(3), "hel"_s);
    CORRADE_COMPARE(a.prefix(2).flags(), StringViewFlags{});
    CORRADE_COMPARE(a.exceptPrefix(2), "llo"_s);
    CORRADE_COMPARE(a.exceptPrefix(2).flags(), StringViewFlag::NullTerminated);
    CORRADE_COMPARE(a.exceptSuffix(2), "hel"_s);
}

template<class T> void StringTest::slicePointer() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    T a = "hello";
    CORRADE_COMPARE(a.slice(a.data() + 1, a.data() + 4), "ell"_s);
    CORRADE_COMPARE(a.sliceSize(a.data() + 1, 3), "ell"_s);
    CORRADE_COMPARE(a.prefix(a.data() + 3), "hel"_s);
    CORRADE_COMPARE(a.prefix(a.data() + 2).flags(), StringViewFlags{});
    CORRADE_COMPARE(a.suffix(a.data() + 2), "llo"_s);
    CORRADE_COMPARE(a.suffix(a.data() + 2).flags(), StringViewFlag::NullTerminated);
}

template<class T> void StringTest::sliceZeroNullPointerAmbiguity() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    T a = "hello";

    /* These should all unambigously pick the std::size_t overloads, not the
       T* overloads */

    CORRADE_COMPARE(a.sliceSize(0, 3), "hel"_s);

    StringView c = a.prefix(0);
    CORRADE_COMPARE(c.size(), 0);
    CORRADE_COMPARE(c.data(), static_cast<const void*>(a.data()));

    /** @todo suffix(0), once the non-deprecated suffix(std::size_t size) is a
        thing */
}

void StringTest::splitCharacter() {
    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    const String ca = "ab//c/def";
    {
        Array<StringView> s = ca.split('/');
        CORRADE_COMPARE_AS(s, (StringIterable{"ab", "", "c", "def"}),
            TestSuite::Compare::Container);
        CORRADE_COMPARE(s[0].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[1].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[2].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[3].flags(), StringViewFlag::NullTerminated);
    } {
        Array<StringView> s = ca.splitWithoutEmptyParts('/');
        CORRADE_COMPARE_AS(s, (StringIterable{"ab", "c", "def"}),
            TestSuite::Compare::Container);
        CORRADE_COMPARE(s[0].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[1].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[2].flags(), StringViewFlag::NullTerminated);
    }

    String a = "ab//c/def";
    CORRADE_COMPARE_AS(a.split('/'),
        (StringIterable{"ab", {}, "c", "def"}),
        TestSuite::Compare::Container);
    CORRADE_COMPARE_AS(a.splitWithoutEmptyParts('/'),
        (StringIterable{"ab", "c", "def"}),
        TestSuite::Compare::Container);
}

void StringTest::splitString() {
    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior or empty delimiter assertions, only the
       basics and flag propagation */

    const String ca = "ab::::c::def";
    {
        Array<StringView> s = ca.split("::");
        CORRADE_COMPARE_AS(s, (StringIterable{"ab", "", "c", "def"}),
            TestSuite::Compare::Container);
        CORRADE_COMPARE(s[0].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[1].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[2].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[3].flags(), StringViewFlag::NullTerminated);
    } {
        /** @todo splitWithoutEmptyParts(), once it takes the delimiter as a
            whole */
    }

    String a = "ab::::c::def";
    CORRADE_COMPARE_AS(a.split("::"),
        (StringIterable{"ab", {}, "c", "def"}),
        TestSuite::Compare::Container);
    /** @todo splitWithoutEmptyParts(), once it takes the delimiter as a
        whole */
}

void StringTest::splitOnAny() {
    constexpr StringView delimiters = ".:;"_s;

    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    const String ca = "ab.:c;def";
    {
        Array<StringView> s = ca.splitOnAnyWithoutEmptyParts(delimiters);
        CORRADE_COMPARE_AS(s, (StringIterable{"ab", "c", "def"}),
            TestSuite::Compare::Container);
        CORRADE_COMPARE(s[0].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[1].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[2].flags(), StringViewFlag::NullTerminated);
    }

    String a = "ab.:c;def";
    CORRADE_COMPARE_AS(a.splitOnAnyWithoutEmptyParts(delimiters),
        (StringIterable{"ab", "c", "def"}),
        TestSuite::Compare::Container);
}

void StringTest::splitOnWhitespace() {
    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    const String ca = "ab\n  c\t\rdef";
    {
        Array<StringView> s = ca.splitOnWhitespaceWithoutEmptyParts();
        CORRADE_COMPARE_AS(s, StringIterable({"ab", "c", "def"}),
            TestSuite::Compare::Container);
        CORRADE_COMPARE(s[0].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[1].flags(), StringViewFlags{});
        CORRADE_COMPARE(s[2].flags(), StringViewFlag::NullTerminated);
    }

    String a = "ab\n  c\t\rdef";
    CORRADE_COMPARE_AS(a.splitOnWhitespaceWithoutEmptyParts(),
        (StringIterable{"ab", "c", "def"}),
        TestSuite::Compare::Container);
}

void StringTest::partitionCharacter() {
    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    const String ca = "ab=c=d";
    {
        Array3<StringView> p1 = ca.partition('=');
        Array3<StringView> p2 = ca.partitionLast('=');
        CORRADE_COMPARE_AS(p1, (StringIterable{"ab", "=", "c=d"}),
            TestSuite::Compare::Container);
        CORRADE_COMPARE_AS(p2, (StringIterable{"ab=c", "=", "d"}),
            TestSuite::Compare::Container);
        CORRADE_COMPARE(p1[0].flags(), StringViewFlags{});
        CORRADE_COMPARE(p2[0].flags(), StringViewFlags{});
        CORRADE_COMPARE(p1[1].flags(), StringViewFlags{});
        CORRADE_COMPARE(p2[1].flags(), StringViewFlags{});
        CORRADE_COMPARE(p1[2].flags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(p2[2].flags(), StringViewFlag::NullTerminated);
    }

    String a = "ab=c=d";
    CORRADE_COMPARE_AS(a.partition('='),
        (StringIterable{"ab", "=", "c=d"}),
        TestSuite::Compare::Container);
    CORRADE_COMPARE_AS(a.partitionLast('='),
        (StringIterable{"ab=c", "=", "d"}),
        TestSuite::Compare::Container);
}

void StringTest::partitionString() {
    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    const String ca = "ab::c::d";
    {
        Array3<StringView> p1 = ca.partition("::");
        Array3<StringView> p2 = ca.partitionLast("::");
        CORRADE_COMPARE_AS(p1, (StringIterable{"ab", "::", "c::d"}),
            TestSuite::Compare::Container);
        CORRADE_COMPARE_AS(p2, (StringIterable{"ab::c", "::", "d"}),
            TestSuite::Compare::Container);
        CORRADE_COMPARE(p1[0].flags(), StringViewFlags{});
        CORRADE_COMPARE(p2[0].flags(), StringViewFlags{});
        CORRADE_COMPARE(p1[1].flags(), StringViewFlags{});
        CORRADE_COMPARE(p2[1].flags(), StringViewFlags{});
        CORRADE_COMPARE(p1[2].flags(), StringViewFlag::NullTerminated);
        CORRADE_COMPARE(p2[2].flags(), StringViewFlag::NullTerminated);
    }

    String a = "ab::c::d";
    CORRADE_COMPARE_AS(a.partition("::"),
        (StringIterable{"ab", "::", "c::d"}),
        TestSuite::Compare::Container);
    CORRADE_COMPARE_AS(a.partitionLast("::"),
        (StringIterable{"ab::c", "::", "d"}),
        TestSuite::Compare::Container);
}

void StringTest::add() {
    /* Tests the StringView API as it's better to do it here instead of in
       StringViewTest where we would need to include String */

    CORRADE_COMPARE(""_s + ""_s, "");
    CORRADE_COMPARE("hello"_s + ""_s, "hello");
    CORRADE_COMPARE(""_s + "hello"_s, "hello");
    CORRADE_COMPARE("hello"_s + "world"_s, "helloworld");
}

void StringTest::addNullViews() {
    /* Test that these don't trigger bullying from UBSan (memcpy called with
       null pointers) */

    CORRADE_COMPARE(StringView{} + StringView{}, "");
    CORRADE_COMPARE("hello"_s + nullptr, "hello");
    CORRADE_COMPARE(nullptr + "hello"_s, "hello");
}

void StringTest::multiply() {
    /* Tests the StringView API as it's better to do it here instead of in
       StringViewTest where we would need to include String */

    CORRADE_COMPARE(""_s*37, "");
    CORRADE_COMPARE(37*""_s, "");
    CORRADE_COMPARE("hello"_s*0, "");
    CORRADE_COMPARE(0*"hello"_s, "");
    CORRADE_COMPARE("hello"_s*5, "hellohellohellohellohello");
    CORRADE_COMPARE(5*"hello"_s, "hellohellohellohellohello");
}

void StringTest::multiplyNullView() {
    /* Test that these don't trigger bullying from UBSan (memcpy called with
       null pointers) */

    CORRADE_COMPARE(StringView{}*37, "");
    CORRADE_COMPARE(37*StringView{}, "");
}

void StringTest::join() {
    /* Tests also the StringView API as it's better to do it here instead of in
       StringViewTest where we would need to include String */

    /* Empty */
    CORRADE_COMPARE(", "_s.join({}), "");
    CORRADE_COMPARE(", "_s.joinWithoutEmptyParts({}), "");

    /* One empty value */
    CORRADE_COMPARE(", "_s.join({""}), "");
    CORRADE_COMPARE(", "_s.joinWithoutEmptyParts({""}), "");

    /* Two empty values */
    CORRADE_COMPARE(", "_s.join({"", ""}), ", ");
    CORRADE_COMPARE(", "_s.joinWithoutEmptyParts({"", ""}), "");

    /* One value */
    CORRADE_COMPARE(", "_s.join({"abcdef"}), "abcdef");
    CORRADE_COMPARE(", "_s.joinWithoutEmptyParts({"abcdef"}), "abcdef");

    /* Common case */
    CORRADE_COMPARE(", "_s.join({"ab", "c", "def"}),
        "ab, c, def");
    CORRADE_COMPARE(", "_s.joinWithoutEmptyParts({"ab", "c", "def"}),
        "ab, c, def");

    /* Empty parts, also the overload directly on a String, and on a
       MutableStringView to verify they delegate to the right thing */
    CORRADE_COMPARE(", "_s.join({"ab", "", "c", "def", "", ""}),
        "ab, , c, def, , ");
    CORRADE_COMPARE(String{", "}.join({"ab", "", "c", "def", "", ""}),
        "ab, , c, def, , ");
    CORRADE_COMPARE(MutableStringView{String{", "}}.join({"ab", "", "c", "def", "", ""}),
        "ab, , c, def, , ");
    CORRADE_COMPARE(", "_s.joinWithoutEmptyParts({"ab", "", "c", "def", "", ""}),
        "ab, c, def");
    CORRADE_COMPARE(String{", "}.joinWithoutEmptyParts({"ab", "", "c", "def", "", ""}),
        "ab, c, def");
    CORRADE_COMPARE(MutableStringView{String{", "}}.joinWithoutEmptyParts({"ab", "", "c", "def", "", ""}),
        "ab, c, def");
}

void StringTest::joinNullViews() {
    /* Test that these don't trigger bullying from UBSan (memcpy called with
       null pointers) */

    /* Null values */
    CORRADE_COMPARE(", "_s.join({nullptr, nullptr}), ", ");
    CORRADE_COMPARE(", "_s.joinWithoutEmptyParts({nullptr, nullptr}), "");

    /* Null joiner */
    CORRADE_COMPARE(StringView{nullptr}.join({"ab", "c", "def"}),
        "abcdef");
    CORRADE_COMPARE(StringView{nullptr}.joinWithoutEmptyParts({"ab", "c", "def"}),
        "abcdef");
}

void StringTest::hasPrefix() {
    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics */

    CORRADE_VERIFY(String{"overcomplicated"}.hasPrefix("over"));
    CORRADE_VERIFY(!String{"overcomplicated"}.hasPrefix("oven"));

    CORRADE_VERIFY(String{"hello"}.hasPrefix('h'));
    CORRADE_VERIFY(!String{"hello"}.hasPrefix('e'));
}

void StringTest::hasSuffix() {
    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics */

    CORRADE_VERIFY(String{"overcomplicated"}.hasSuffix("complicated"));
    CORRADE_VERIFY(!String{"overcomplicated"}.hasSuffix("somplicated"));

    CORRADE_VERIFY(String{"hello"}.hasSuffix('o'));
    CORRADE_VERIFY(!String{"hello"}.hasSuffix('l'));
}

template<class T> void StringTest::exceptPrefix() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    T a{"overcomplicated"};

    typename ConstTraits<T>::ViewType b = a.exceptPrefix("over");
    CORRADE_COMPARE(b, "complicated"_s);
    CORRADE_COMPARE(b.flags(), StringViewFlag::NullTerminated);
}

void StringTest::exceptPrefixInvalid() {
    CORRADE_SKIP_IF_NO_ASSERT();

    String a{"overcomplicated"};
    const String ca{"overcomplicated"};

    Containers::String out;
    Error redirectOutput{&out};
    a.exceptPrefix("complicated");
    ca.exceptPrefix("complicated");
    /* Assert is coming from StringView */
    CORRADE_COMPARE(out,
        "Containers::StringView::exceptPrefix(): string doesn't begin with complicated\n"
        "Containers::StringView::exceptPrefix(): string doesn't begin with complicated\n");
}

CORRADE_HAS_TYPE(CanExceptPrefixBeCalledWith, decltype(String{}.exceptPrefix(std::declval<T>())));

void StringTest::exceptPrefixDisabledOverloads() {
    /* I thought I could use std::is_invocable (or a C++11 backport of it) to
       easily test this. Boy I was wrong, that API is absolutely useless, while
       the CORRADE_HAS_TYPE() macro is the best thing ever.

       Same as exceptSuffixDisabledOverloads(), please keep consistent */

    CORRADE_VERIFY(CanExceptPrefixBeCalledWith<std::size_t>::value);
    /* Doing exceptPrefix(1) should work */
    CORRADE_VERIFY(CanExceptPrefixBeCalledWith<int>::value);
    /* Borderline dangerous, but decltype('a') == char, so this should be ok */
    CORRADE_VERIFY(CanExceptPrefixBeCalledWith<signed char>::value);
    /* exceptPrefix('a') should be disallowed because it's too easy to misuse
       e.g. as exceptPrefix(std::toupper('a')), resulting in exceptPrefix(65) */
    CORRADE_VERIFY(!CanExceptPrefixBeCalledWith<char>::value);
}

template<class T> void StringTest::exceptSuffix() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    String a{"overcomplicated"};

    typename ConstTraits<T>::ViewType b = a.exceptSuffix("complicated");
    CORRADE_COMPARE(b, "over"_s);
    CORRADE_COMPARE(b.flags(), StringViewFlags{});

    CORRADE_COMPARE(a.exceptSuffix("").flags(), StringViewFlag::NullTerminated);
}

void StringTest::exceptSuffixInvalid() {
    CORRADE_SKIP_IF_NO_ASSERT();

    String a{"overcomplicated"};
    const String ca{"overcomplicated"};

    Containers::String out;
    Error redirectOutput{&out};
    a.exceptSuffix("over");
    ca.exceptSuffix("over");
    /* Assert is coming from StringView */
    CORRADE_COMPARE(out,
        "Containers::StringView::exceptSuffix(): string doesn't end with over\n"
        "Containers::StringView::exceptSuffix(): string doesn't end with over\n");
}

CORRADE_HAS_TYPE(CanExceptSuffixBeCalledWith, decltype(String{}.exceptSuffix(std::declval<T>())));

void StringTest::exceptSuffixDisabledOverloads() {
    /* I thought I could use std::is_invocable (or a C++11 backport of it) to
       easily test this. Boy I was wrong, that API is absolutely useless, while
       the CORRADE_HAS_TYPE() macro is the best thing ever.

       Same as exceptPrefixDisabledOverloads(), please keep consistent */

    CORRADE_VERIFY(CanExceptSuffixBeCalledWith<std::size_t>::value);
    /* Ding exceptSuffix(1) should work */
    CORRADE_VERIFY(CanExceptSuffixBeCalledWith<int>::value);
    /* Borderline dangerous, but decltype('a') == char, so this should be ok */
    CORRADE_VERIFY(CanExceptSuffixBeCalledWith<signed char>::value);
    /* exceptPrefix('a') should be disallowed because it's too easy to misuse
       e.g. as exceptPrefix(std::toupper('a')), resulting in exceptPrefix(65) */
    CORRADE_VERIFY(!CanExceptSuffixBeCalledWith<char>::value);
}

template<class T> void StringTest::trimmed() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    T a{" \t abc \n "};
    {
        typename ConstTraits<T>::ViewType trimmed = a.trimmedPrefix();
        CORRADE_COMPARE(trimmed, "abc \n "_s);
        CORRADE_COMPARE(trimmed.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType trimmed = a.trimmedSuffix();
        CORRADE_COMPARE(trimmed, " \t abc"_s);
        CORRADE_COMPARE(trimmed.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType trimmed = a.trimmed();
        CORRADE_COMPARE(trimmed, "abc"_s);
        CORRADE_COMPARE(trimmed.flags(), StringViewFlags{});
    }

    T b{"oubya"};
    {
        typename ConstTraits<T>::ViewType trimmed = b.trimmedPrefix("aeiyou");
        CORRADE_COMPARE(trimmed, "bya"_s);
        CORRADE_COMPARE(trimmed.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType trimmed = b.trimmedSuffix("aeiyou");
        CORRADE_COMPARE(trimmed, "oub"_s);
        CORRADE_COMPARE(trimmed.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType trimmed = b.trimmed("aeiyou");
        CORRADE_COMPARE(trimmed, "b"_s);
        CORRADE_COMPARE(trimmed.flags(), StringViewFlags{});
    }
}

template<class T> void StringTest::find() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    /* Duplicated word to test that it's not delegated to findLast() */
    T a{"hello hello world"};
    {
        CORRADE_VERIFY(a.contains("hello"));

        typename ConstTraits<T>::ViewType found = a.find("hello");
        CORRADE_COMPARE(found, "hello"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        CORRADE_VERIFY(a.contains("world"));

        typename ConstTraits<T>::ViewType found = a.find("world");
        CORRADE_COMPARE(found, "world"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 12));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        CORRADE_VERIFY(!a.contains("cursed"));

        typename ConstTraits<T>::ViewType found = a.find("cursed");
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(nullptr));
    } {
        CORRADE_VERIFY(a.contains('h'));

        typename ConstTraits<T>::ViewType found = a.find('h');
        CORRADE_COMPARE(found, "h"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        CORRADE_VERIFY(a.contains('d'));

        typename ConstTraits<T>::ViewType found = a.find('d');
        CORRADE_COMPARE(found, "d"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 16));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        CORRADE_VERIFY(!a.contains('c'));

        typename ConstTraits<T>::ViewType found = a.find('c');
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(nullptr));
    }
}

template<class T> void StringTest::findOr() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* Mostly the same as find(), except that we expect a different pointer in
       case of failure. Non-failure cases are kept to verify it's not
       propagated to findLastOr() by accident. */

    T a{"hello hello world"};
    {
        typename ConstTraits<T>::ViewType found = a.findOr("hello", a.end());
        CORRADE_COMPARE(found, "hello"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType found = a.findOr("world", a.end());
        CORRADE_COMPARE(found, "world"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 12));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType found = a.findOr("cursed", a.end());
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.end()));
    } {
        typename ConstTraits<T>::ViewType found = a.findOr('h', a.end());
        CORRADE_COMPARE(found, "h"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType found = a.findOr('d', a.end());
        CORRADE_COMPARE(found, "d"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 16));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType found = a.findOr('c', a.end());
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.end()));
    }
}

template<class T> void StringTest::findLast() {
    /* Mostly the same as find(), except that we don't test contains() which is
       implemented with the same algorithm as find() */

    setTestCaseTemplateName(ConstTraits<T>::name());

    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    /* Duplicated word to test that it's not delegated to find() */
    T a{"hello world world"};
    {
        typename ConstTraits<T>::ViewType found = a.findLast("hello");
        CORRADE_COMPARE(found, "hello"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType found = a.findLast("world");
        CORRADE_COMPARE(found, "world"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 12));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType found = a.findLast("cursed");
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(nullptr));
    } {
        typename ConstTraits<T>::ViewType found = a.findLast('h');
        CORRADE_COMPARE(found, "h"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType found = a.findLast('d');
        CORRADE_COMPARE(found, "d"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 16));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType found = a.findLast('c');
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(nullptr));
    }
}

template<class T> void StringTest::findLastOr() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* Mostly the same as findLast(), except that we expect a different pointer
       in case of failure. Non-failure cases are kept to verify it's not
       propagated to findOr() by accident. */

    T a{"hello world world"};
    {
        typename ConstTraits<T>::ViewType found = a.findLastOr("hello", a.end());
        CORRADE_COMPARE(found, "hello"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType found = a.findLastOr("world", a.end());
        CORRADE_COMPARE(found, "world"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 12));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType found = a.findLastOr("cursed", a.end());
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.end()));
    } {
        typename ConstTraits<T>::ViewType found = a.findLastOr('h', a.end());
        CORRADE_COMPARE(found, "h"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType found = a.findLastOr('d', a.end());
        CORRADE_COMPARE(found, "d"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 16));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType found = a.findLastOr('c', a.end());
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.end()));
    }
}

template<class T> void StringTest::findAny() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    /* Duplicated characters to test that it's not delegated to findAnyOr() */
    T a{"hello world"};
    {
        CORRADE_VERIFY(a.containsAny("eh!"));

        typename ConstTraits<T>::ViewType found = a.findAny("eh!");
        CORRADE_COMPARE(found, "h"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        CORRADE_VERIFY(a.containsAny("bud"));

        typename ConstTraits<T>::ViewType found = a.findAny("bud");
        CORRADE_COMPARE(found, "d"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 10));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        CORRADE_VERIFY(!a.containsAny("pub"));

        typename ConstTraits<T>::ViewType found = a.findAny("pub");
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(nullptr));
    }
}

template<class T> void StringTest::findAnyOr() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* Mostly the same as findAny(), except that we expect a different pointer
       in case of failure. Non-failure cases are kept to verify it's not
       propagated to findLastAnyOr() by accident. */

    T a{"hello world"};
    {
        typename ConstTraits<T>::ViewType found = a.findAnyOr("eh!", a.end());
        CORRADE_COMPARE(found, "h"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType found = a.findAnyOr("bud", a.end());
        CORRADE_COMPARE(found, "d"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 10));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType found = a.findAnyOr("pub", a.end());
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.end()));
    }
}

template<class T> void StringTest::findLastAny() {
    /* Mostly the same as findAny(), except that we don't test containsAny()
       which is implemented with the same algorithm as findAny() */

    setTestCaseTemplateName(ConstTraits<T>::name());

    /* These rely on StringView conversion and then delegate there so we don't
       need to verify SSO behavior, only the basics and flag propagation */

    /* Duplicated characters to test that it's not delegated to
       findLastAnyOr() */
    T a{"hello world"};
    {
        typename ConstTraits<T>::ViewType found = a.findLastAny("uhu");
        CORRADE_COMPARE(found, "h"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType found = a.findLastAny("duh!");
        CORRADE_COMPARE(found, "d"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 10));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType found = a.findLastAny("pub");
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(nullptr));
    }
}

template<class T> void StringTest::findLastAnyOr() {
    setTestCaseTemplateName(ConstTraits<T>::name());

    /* Mostly the same as findLastAny(), except that we expect a different
       pointer in case of failure. Non-failure cases are kept to verify it's
       not propagated to findAnyOr() by accident. */

    T a{"hello world"};
    {
        typename ConstTraits<T>::ViewType found = a.findLastAnyOr("uhu", a.end());
        CORRADE_COMPARE(found, "h"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data()));
        CORRADE_COMPARE(found.flags(), StringViewFlags{});
    } {
        typename ConstTraits<T>::ViewType found = a.findLastAnyOr("duh!", a.end());
        CORRADE_COMPARE(found, "d"_s);
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.data() + 10));
        CORRADE_COMPARE(found.flags(), StringViewFlag::NullTerminated);
    } {
        typename ConstTraits<T>::ViewType found = a.findLastAnyOr("pub", a.end());
        CORRADE_VERIFY(found.isEmpty());
        CORRADE_COMPARE(found.data(), static_cast<const void*>(a.end()));
    }
}

void StringTest::count() {
    /* It relies on StringView conversion and then delegates there so we don't
       need to verify SSO behavior, only the basics */

    String a{"hello world"};
    CORRADE_COMPARE(a.count('\0'), 0);
    CORRADE_COMPARE(a.count('d'), 1);
    CORRADE_COMPARE(a.count('l'), 3);
}

void StringTest::release() {
    String a = "Allocated hello for a verbose world";

    const void* data = a.data();
    const char* released = a.release();
    delete[] released;
    CORRADE_COMPARE(released, data);

    /* Post-release state should be the same as of a default-constructed
       instance -- with zero size, but a non-null null-terminated data */
    CORRADE_VERIFY(a.isSmall());
    CORRADE_COMPARE(a.size(), 0);
    CORRADE_VERIFY(a.data());
    CORRADE_COMPARE(a.data()[0], '\0');
}

void StringTest::releaseDeleterSmall() {
    CORRADE_SKIP_IF_NO_ASSERT();

    String a;
    CORRADE_VERIFY(a.isSmall());

    Containers::String out;
    Error redirectError{&out};
    a.deleter();
    a.release();
    CORRADE_COMPARE(out,
        "Containers::String::deleter(): cannot call on a SSO instance\n"
        "Containers::String::release(): cannot call on a SSO instance\n");
}

void StringTest::defaultDeleter() {
    String a{Corrade::ValueInit, 50};
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_VERIFY(a.deleter() == nullptr);
}

int CustomDeleterCallCount = 0;

void StringTest::customDeleter() {
    CustomDeleterCallCount = 0;
    char data[26]{'\xfc'};
    CORRADE_VERIFY(true); /* to register proper function name */

    {
        String a{data, 25, [](char* data, std::size_t size) {
            CORRADE_VERIFY(data);
            CORRADE_COMPARE(data[0], '\xfc');
            CORRADE_COMPARE(size, 25);
            ++CustomDeleterCallCount;
        }};
        CORRADE_VERIFY(a.data() == data);
        CORRADE_COMPARE(a.size(), 25);
        CORRADE_COMPARE(CustomDeleterCallCount, 0);
    }

    CORRADE_COMPARE(CustomDeleterCallCount, 1);
}

void StringTest::customDeleterZeroSize() {
    CustomDeleterCallCount = 0;
    /* Zero size forces us to have data[0] a null terminator, so use the second
       element for an "expected content" check */
    char data[26]{0, '\xfc'};
    CORRADE_VERIFY(true); /* to register proper function name */

    {
        String a{data, 0, [](char* data, std::size_t size) {
            CORRADE_VERIFY(data);
            CORRADE_COMPARE(data[1], '\xfc');
            CORRADE_COMPARE(size, 0);
            ++CustomDeleterCallCount;
        }};
        CORRADE_VERIFY(a.data() == data);
        CORRADE_COMPARE(a.size(), 0);
        CORRADE_COMPARE(CustomDeleterCallCount, 0);
    }

    /* The deleter should unconditionally get called here as well, consistently
       with what Array does */
    CORRADE_COMPARE(CustomDeleterCallCount, 1);
}

void StringTest::customDeleterMovedOutInstance() {
    CustomDeleterCallCount = 0;
    char data[26]{};
    CORRADE_VERIFY(true); /* to register proper function name */

    {
        String a{data, 25, [](char*, std::size_t) {
            ++CustomDeleterCallCount;
        }};
        CORRADE_COMPARE(CustomDeleterCallCount, 0);

        String b = Utility::move(a);
        CORRADE_COMPARE(CustomDeleterCallCount, 0);
    }

    /* The deleter got reset to nullptr in a, which means the function gets
       called only once, consistently with what Array does */
    CORRADE_COMPARE(CustomDeleterCallCount, 1);
}

void StringTest::dataAlignment() {
    /* A heap-allocated string will be always aligned to the allocation
       granularity, which is usually 2*sizeof(void*) and always at least
       sizeof(void*). It's good to have this property to avoid unnecessary
       special-casing in various SIMD / SWAR algorithms. */
    String a{AllocatedInit, "hello!"};
    CORRADE_VERIFY(!a.isSmall());
    CORRADE_COMPARE_AS(a.data(), sizeof(void*), TestSuite::Compare::Aligned);

    /* A SSO string should be aligned to sizeof(void*), since the data storage
       is inside the class that's pointer-aligned. Only on LE though. */
    String b{"hello!"};
    CORRADE_VERIFY(b.isSmall());
    {
        #ifdef CORRADE_TARGET_BIG_ENDIAN
        CORRADE_EXPECT_FAIL("The internal class layout doesn't allow to have the SSO data aligned on Big Endian.");
        #endif
        CORRADE_COMPARE_AS(b.data(), sizeof(void*), TestSuite::Compare::Aligned);
    }
}

}}}}

CORRADE_TEST_MAIN(Corrade::Containers::Test::StringTest)
